<?php

// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

////////////////////
/// autofeedback ///
////////////////////

/// QUESTION TYPE CLASS //////////////////

///
/// This class contains some special features in order to make the
/// question type embeddable within a multianswer (cloze) question
///
/**
 * @package questionbank
 * @subpackage questiontypes
 */
require_once("$CFG->dirroot/question/type/questiontype.php");
require_once($CFG->dirroot.'/question/type/autofeedback/autofeedback.php');
class question_autofeedback_qtype extends default_questiontype {

    function name() {
        return 'autofeedback';
    }
    
    function requires_qtypes() {
        return array('shortanswer');
    }
    
    function has_wildcards_in_responses($question, $subqid) {
        return false;
    }

    function extra_question_fields() {
        return array('question_autofeedback', 'answers', 'usecase', 'typopenalty', 'oddpenalty', 'missingpenalty', 'misplacedpenalty');
    }

    function questionid_column_name() {
        return 'question';
    }

    /**
     * When move the category of questions, the belonging files should be moved as well
     * @param object $question, question information
     * @param object $newcategory, target category information
     */
    function move_files($question, $newcategory) {
        global $DB;
        parent::move_files($question, $newcategory);
    
        $fs = get_file_storage();
        // process files in answer
        if (!$oldanswers = $DB->get_records('question_answers', array('question' =>  $question->id), 'id ASC')) {
            $oldanswers = array();
        }
        $component = 'question';
        $filearea = 'answerfeedback';
        foreach ($oldanswers as $answer) {
            $files = $fs->get_area_files($question->contextid, $component, $filearea, $answer->id);
            foreach ($files as $storedfile) {
                if (!$storedfile->is_directory()) {
                    $newfile = new object();
                    $newfile->contextid = (int)$newcategory->contextid;
                    $fs->create_file_from_storedfile($newfile, $storedfile);
                    $storedfile->delete();
                }
            }
        }
    }

    function get_lexemes ($answerstring) {
        $lexemes = array();
        echo 'getting lexemes ';
        $item_r_e = '/([^ \t\(\),;]+)|([\(\),;:])/';
        $item_number = preg_match_all($item_r_e, $answerstring, $lexemes);
        
        return $lexemes[0];
    }
    
    function save_question_options($question) {
        global $DB;
        $result = new stdClass;
        $context = $question->context;
        
        if (!$oldanswers = $DB->get_records('question_answers', array('question' =>  $question->id), 'id ASC')) {
            $oldanswers = array();
        }

        $answers = array();
        $maxfraction = -1;

        // Insert all the new answers and lexemes for each of them.
        echo 'saving answers<br>';
        echo '<pre> question:';
        //print_r($question);
        foreach ($question->answer as $key => $answerdata) {
            if (trim($answerdata) == '' && $question->fraction[$key] == 0 &&
                    html_is_blank($question->feedback[$key]['text'])) {
                continue;
            }
            
            
            echo '<br>______<br>';
            //print_r($key);
            echo '<br>';
            //print_r($answerdata);
        
            
            $answer = new stdClass;
            $answer->answer = 'someanswer';
            $answer->question = $question->id;
            $answer->fraction = 1;
            $answer->feedback = '';
            $answer->id = $DB->insert_record('question_answers', $answer);
            
            $answer->answer   = trim($answerdata);
            $answer->fraction = $question->fraction[$key];
            $answer->feedback = $this->import_or_save_files($question->feedback[$key],
                    $context, 'question', 'answerfeedback', $answer->id);
            $answer->feedbackformat = $question->feedback[$key]['format'];
            $DB->update_record('question_answers', $answer);
            $answers[] = $answer->id;
            if ($question->fraction[$key] > $maxfraction) {
                $maxfraction = $question->fraction[$key];
            }
            
            // insert lexemes
            echo '<br>getting lexemes<br>';
            //print_r($answerdata);
            $lexemes = $this->get_lexemes((string)$answerdata);    // array of lexeme data
            echo '<br>lexemes: ';
            //print_r($lexemes);
            $lexemenames = explode("\n", $question->lexemedescriptions[$key]);
            echo '<br>names:';
            ////print_r($lexemenames);
echo'            count: '.count($lexemes).' '.count($lexemenames);
            if ( count($lexemes) == count($lexemenames)) {
                echo '<br>count equals<br>';
                for ($i = 0; $i < count($lexemes) ; $i++) {
                    $lexemerecored = new stdClass;
                    $lexemerecord->answerid = $answer->id;
                    $lexemerecord->text = $lexemes[$i];
                    $lexemerecord->name = $lexemenames[$i];
                    $lexemerecord->serialnumber = $i;
                    $DB->insert_record('autofeedback_lexemes', $lexemerecord);
                }
            }
            //$answer->typopenalty = $question
            //$answer->oddpenalty
            $lexemestring = array();
            echo'<br>strings: ';
            $lexemestrings = explode("\n",$question->lexemedescriptions[$key]);
            //print_r($lexemestrings);
            echo '</pre>';
            $lexemetexts =  array();
            $lexemenames = array();
            /*foreach ($lexemestrings as $lexemestring) {
                $space = stripos($lexemestring, ' ');
                $description = substr($lexemestring, $space + 1);
                $name = substr($lexemestring, 0, strlen($lexemestring) - (strlen($description) + 1));
                $answer = new stdClass;
                
                $answer->answer   = $name;
                $answer->question = $question->id;
                $answer->feedback = '';
                $answer->fraction = 1;
                $answer->id = $DB->insert_record("question_answers", $answer);
                //$DB->set_field('question_answers', 'feedback', $description, array('id'=>$answer->id));
                $question->answers[] = $answer;
            }*/
        }
        $question->answers = implode(',', $answers);
        $parentresult = parent::save_question_options($question);
        if($parentresult !== null) { // Parent function returns null if all is OK
            return $parentresult;
        }

        // delete old answer records
        if (!empty($oldanswers)) {
            foreach($oldanswers as $oa) {
                $DB->delete_records('question_answers', array('id' => $oa->id));
            }
        }

        /// Perform sanity checks on fractional grades
        if ($maxfraction != 1) {
            $maxfraction = $maxfraction * 100;
            return $result;
        } else {
            return true;
        }
    }

    function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
        global $CFG;
        $context = $this->get_context_by_category_id($question->category);
    /// This implementation is also used by question type 'numerical'
        $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
        $formatoptions = new stdClass;
        $formatoptions->noclean = true;
        $formatoptions->para = false;
        $nameprefix = $question->name_prefix;

        /// Print question text and media
        $questiontext = format_text($question->questiontext,
                $question->questiontextformat,
                $formatoptions, $cmoptions->course);
        /// Print input controls

        if (isset($state->responses['']) && $state->responses['']!='') {
            $value = ' value="'.s($state->responses['']).'" ';
        } else {
            $value = ' value="" ';
        }
        $inputname = ' name="'.$nameprefix.'" ';

        $feedback = '';
        $class = '';
        $feedbackimg = '';

        if ($options->feedback) {
            $class = 'partiallycorrect';
            $feedback = 'There goes:<br>';
            $feedbackimg = question_get_feedback_image(0);
            //this is OK for the first answer with a good response
            //echo 'about to process. answers: <br><pre>';
            ////print_r($question->options->answers);
            echo '</pre>';
            autofeedback_process($state->responses[''], $question->options->answers, $feedback, $errorcount);
            if (strlen($feedback) == 0) {
                $class = 'correct';
                $feedbackimg = question_get_feedback_image(1);
            }
        }

        /// Removed correct answer, to be displayed later MDL-7496
        include($this->get_display_html_path());
    }

    function get_display_html_path() {
        global $CFG;
        return $CFG->dirroot.'/question/type/autofeedback/display.html';
    }

    function check_response(&$question, &$state) {
        foreach($question->options->answers as $aid => $answer) {
            if ($this->test_response($question, $state, $answer)) {
                return $aid;
            }
        }
        return false;
    }

    function compare_responses($question, $state, $teststate) {
        if (isset($state->responses['']) && isset($teststate->responses[''])) {
            return $state->responses[''] === $teststate->responses[''];
        }
        return false;
    }

    function test_response(&$question, $state, $answer) {
        // Trim the response before it is saved in the database. See MDL-10709
        $state->responses[''] = trim($state->responses['']);
        return $this->compare_string_with_wildcard($state->responses[''],
                $answer->answer, !$question->options->usecase);
    }

    function compare_string_with_wildcard($string, $pattern, $ignorecase) {
        // Break the string on non-escaped asterisks.
        $bits = preg_split('/(?<!\\\\)\*/', $pattern);
        // Escape regexp special characters in the bits.
        $excapedbits = array();
        foreach ($bits as $bit) {
            $excapedbits[] = preg_quote(str_replace('\*', '*', $bit));
        }
        // Put it back together to make the regexp.
        $regexp = '|^' . implode('.*', $excapedbits) . '$|u';

        // Make the match insensitive if requested to.
        if ($ignorecase) {
            $regexp .= 'i';
        }

        return preg_match($regexp, trim($string));
    }

    /*
     * Override the parent class method, to remove escaping from asterisks.
     */
    function get_correct_responses(&$question, &$state) {
        $response = parent::get_correct_responses($question, $state);
        if (is_array($response)) {
            $response[''] = str_replace('\*', '*', $response['']);
        }
        return $response;
    }
    /**
     * @param object $question
     * @return mixed either a integer score out of 1 that the average random
     * guess by a student might give or an empty string which means will not
     * calculate.
     */
    function get_random_guess_score($question) {
        $answers = &$question->options->answers;
        foreach($answers as $aid => $answer) {
            if ('*' == trim($answer->answer)){
                return $answer->fraction;
            }
        }
        return 0;
    }

/// RESTORE FUNCTIONS /////////////////

    /*
     * Restores the data in the question
     *
     * This is used in question/restorelib.php
     */
    function restore($old_question_id,$new_question_id,$info,$restore) {
        global $DB;

        $status = parent::restore($old_question_id, $new_question_id, $info, $restore);

        if ($status) {
            $extraquestionfields = $this->extra_question_fields();
            $questionextensiontable = array_shift($extraquestionfields);

            //We have to recode the answers field (a list of answers id)
            $questionextradata = $DB->get_record($questionextensiontable, array($this->questionid_column_name() => $new_question_id));
            if (isset($questionextradata->answers)) {
                $answers_field = "";
                $in_first = true;
                $tok = strtok($questionextradata->answers, ",");
                while ($tok) {
                    // Get the answer from backup_ids
                    $answer = backup_getid($restore->backup_unique_code,"question_answers",$tok);
                    if ($answer) {
                        if ($in_first) {
                            $answers_field .= $answer->new_id;
                            $in_first = false;
                        } else {
                            $answers_field .= ",".$answer->new_id;
                        }
                    }
                    // Check for next
                    $tok = strtok(",");
                }
                // We have the answers field recoded to its new ids
                $questionextradata->answers = $answers_field;
                // Update the question
                $DB->update_record($questionextensiontable, $questionextradata);
            }
        }

        return $status;
    }

    /**
    * Prints the score obtained and maximum score available plus any penalty
    * information
    *
    * This function prints a summary of the scoring in the most recently
    * graded state (the question may not have been submitted for marking at
    * the current state). The default implementation should be suitable for most
    * question types.
    * @param object $question The question for which the grading details are
    *                         to be rendered. Question type specific information
    *                         is included. The maximum possible grade is in
    *                         ->maxgrade.
    * @param object $state    The state. In particular the grading information
    *                          is in ->grade, ->raw_grade and ->penalty.
    * @param object $cmoptions
    * @param object $options  An object describing the rendering options.
    */
    function print_question_grading_details(&$question, &$state, $cmoptions, $options) {
        /* The default implementation prints the number of marks if no attempt
        has been made. Otherwise it displays the grade obtained out of the
        maximum grade available and a warning if a penalty was applied for the
        attempt and displays the overall grade obtained counting all previous
        responses (and penalties) */

        global $QTYPES ;
        // MDL-7496 show correct answer after "Incorrect"
        $correctanswer = '';
        if ($correctanswers =  $QTYPES[$question->qtype]->get_correct_responses($question, $state)) {
            if ($options->readonly && $options->correct_responses) {
                $delimiter = '';
                if ($correctanswers) {
                    foreach ($correctanswers as $ca) {
                        $correctanswer .= $delimiter.$ca;
                        $delimiter = ', ';
                    }
                }
            }
        }

        if (QUESTION_EVENTDUPLICATE == $state->event) {
            echo ' ';
            print_string('duplicateresponse', 'quiz');
        }
        if ($question->maxgrade > 0 && $options->scores) {
            if (question_state_is_graded($state->last_graded)) {
                // Display the grading details from the last graded state
                $grade = new stdClass;
                $grade->cur = question_format_grade($cmoptions, $state->last_graded->grade);
                $grade->max = question_format_grade($cmoptions, $question->maxgrade);
                $grade->raw = question_format_grade($cmoptions, $state->last_graded->raw_grade);
                // let student know wether the answer was correct
                $class = question_get_feedback_class($state->last_graded->raw_grade /
                        $question->maxgrade);
                echo '<div class="correctness ' . $class . '">' . get_string($class, 'quiz');
                if ($correctanswer  != '' && ($class == 'partiallycorrect' || $class == 'incorrect')) {
                    echo ('<div class="correctness">');
                    print_string('correctansweris', 'quiz', s($correctanswer));
                    echo ('</div>');
                }
                echo '</div>';

                echo '<div class="gradingdetails">';
                // print grade for this submission
                print_string('gradingdetails', 'quiz', $grade) ;
                // A unit penalty for numerical was applied so display it
                // a temporary solution for unit rendering in numerical
                // waiting for the new question engine code for a permanent one
                if(isset($state->options->raw_unitpenalty) && $state->options->raw_unitpenalty > 0.0 ){
                    echo ' ';
                    print_string('unitappliedpenalty','qtype_numerical',question_format_grade($cmoptions, $state->options->raw_unitpenalty ));
                }
                if ($cmoptions->penaltyscheme) {
                    // print details of grade adjustment due to penalties
                    if ($state->last_graded->raw_grade > $state->last_graded->grade){
                        echo ' ';
                        print_string('gradingdetailsadjustment', 'quiz', $grade);
                    }
                    // print info about new penalty
                    // penalty is relevant only if the answer is not correct and further attempts are possible
                    if (($state->last_graded->raw_grade < $question->maxgrade) and (QUESTION_EVENTCLOSEANDGRADE != $state->event)) {
                        if ('' !== $state->last_graded->penalty && ((float)$state->last_graded->penalty) > 0.0) {
                            echo ' ' ;
                            print_string('gradingdetailspenalty', 'quiz', question_format_grade($cmoptions, $state->last_graded->penalty));
                        } else {
                            /* No penalty was applied even though the answer was
                            not correct (eg. a syntax error) so tell the student
                            that they were not penalised for the attempt */
                            echo ' ';
                            print_string('gradingdetailszeropenalty', 'quiz');
                        }
                    }
                }
                echo '</div>';
            }
        }
    }

    /**
     * Runs all the code required to set up and save an essay question for testing purposes.
     * Alternate DB table prefix may be used to facilitate data deletion.
     */
    function generate_test($name, $courseid = null) {
        global $DB;
        list($form, $question) = parent::generate_test($name, $courseid);
        $question->category = $form->category;

        $form->questiontext = "What is the purpose of life, the universe, and everything";
        $form->generalfeedback = "Congratulations, you may have solved my biggest problem!";
        $form->penalty = 0.1;
        $form->usecase = false;
        $form->defaultgrade = 1;
        $form->noanswers = 3;
        $form->answer = array('42', 'who cares?', 'Be happy');
        $form->fraction = array(1, 0.6, 0.8);
        $form->feedback = array('True, but what does that mean?', 'Well you do, dont you?', 'Yes, but thats not funny...');
        $form->correctfeedback = 'Excellent!';
        $form->incorrectfeedback = 'Nope!';
        $form->partiallycorrectfeedback = 'Not bad';

        if ($courseid) {
            $course = $DB->get_record('course', array('id' => $courseid));
        }

        return $this->save_question($question, $form, $course);
    }

    function check_file_access($question, $state, $options, $contextid, $component,
            $filearea, $args) {
        if ($component == 'question' && $filearea == 'answerfeedback') {
            $answers = &$question->options->answers;
            if (isset($state->responses[''])) {
                $response = $state->responses[''];
            } else {
                $response = '';
            }
            $answerid = reset($args); // itemid is answer id.
            if (empty($options->feedback)) {
                return false;
            }
            foreach($answers as $answer) {
                if ($this->test_response($question, $state, $answer)) {
                    return true;
                }
            }
            return false;
        } else {
            return parent::check_file_access($question, $state, $options, $contextid, $component,
                    $filearea, $args);
        }
    }
}
//// END OF CLASS ////

//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
question_register_questiontype(new question_autofeedback_qtype());